看到二进制序列可以用各种非数字方式解释,让我们将注意力转向数字。具体来说,我们将从无符号数字开始,这些数字可以解释为零或正数,但它们永远不能为负数(它们没有 符号)。
4.1.1. 小数
我们先不从二进制开始,而是先检查一下我们已经习惯使用的数字系统,即十进制数字系统,它使用 base 为 10。Base 10 意味着解释和表示十进制值的两个重要属性。
- 以 10 为基数的数字中的任何单个数字都存储 10 个唯一值 (0-9) 之一。要存储大于 9 的值,该值必须进位 到左侧的附加数字。例如,如果一位数字从最大值 (9) 开始,我们加 1,则结果需要两位数字 (9 + 1 = 10)。相同的模式适用于任何数字,无论其在数字中的位置如何(例如,5080 + 20 = 5100)。
- 数字中每个数字的位置决定了该数字对于数字总价值的重要性。将从右到左的数字标记为 d0、d1、d2 等,每个连续的数字比下一个数字贡献 十 倍。例如,取值 8425(图 1)。
图 1. 以 10 为基数的数字中每个数字的重要性,使用您在小学时可能给每个数字起的名称。
对于示例值 8425,“个位”中的 5 贡献 5 (5 × 100)。 “十位”的 2 贡献 20 (2 × 101)。 “百位”中的 4 贡献了 400 (4 × 102),最后,“千位”中的 8 贡献了 8000 (8 × 103)。更正式地说,可以将 8425 表示为
(8 × 103) + (4 × 102) + (2 × 101) + (5 × 100)
这种以 10 为底的指数递增模式就是它被称为 base 10 数字系统的原因。将位置编号分配给从右到左从 d0 开始的数字意味着每个数字 di 为总价值贡献 10i 。因此,任何 N 位十进制数的总值可以表示为:
(dN-1 × 10N-1) + (dN-2 × 10N-2) + … + (d2 × 102) + (d1 × 101) + (d0 × 100)
幸运的是,正如我们很快就会看到的,非常相似的模式也适用于其他数字系统。
区分不同进制数基
在我们即将引入第二个数字系统,一个潜在的问题是如何解释数字缺乏清晰度。例如,考虑值 1000。是否应该将该数字解释为十进制值(即 1000)还是二进制值(即 8,原因很快就会解释),这一点并不是很明显。为了帮助澄清,本章的其余部分将明确为所有非十进制数字附加一个前缀。我们很快将介绍二进制(前缀为 0b)和十六进制(使用前缀 0x)。
因此,如果您看到 1000,您应该假设它是十进制“一千”,如果您看到 0b1000,您应该将其解释为二进制数,在本例中为值“八”。
4.1.2. 无符号二进制数
虽然您可能从未考虑过将十进制数描述为 10 的幂的具体公式,但 { 个位, 十位, 百位, 等. } 位的概念应该会让您感到熟悉。幸运的是,类似的术语适用于其他数字系统,例如二进制。当然,其他数字系统中的基数不同,因此每个数字位置对其数值的贡献量不同。
二进制数字系统使用基数 2,而不是十进制的 10。以与我们刚才对十进制相同的方式进行分析,可以发现几个相似之处(用 2 代替 10):
- 以 2 为基数的数字中的任何单个位都存储两个唯一值(0 或 1)之一。要存储大于 1 的值,二进制编码必须进位 到左侧的附加位。例如,如果一位从最大值 (1) 开始,并且我们向其加 1,则结果需要两位 (1 + 1 = 0b10)。相同的模式适用于任何位,无论其在数字中的位置如何(例如,0b100100 + 0b100 = 0b101000)。
- 数字中每一位的位置决定了该位对于数字的数值的重要性。将从右到左的数字标记为 d0、d1、d2 等,每个连续位的贡献比下一位多 两倍 。
第一点意味着二进制计数遵循与十进制相同的模式:通过简单地枚举值并添加数字(位)。由于本节重点介绍_无符号_数字(仅零和正数),因此很自然地从零开始计数。 表 1 显示了如何计算二进制中的前几个自然数。从表中可以看出,以二进制计数很快就会增加位数。直观上,这种增长是有道理的,因为每个二进制数字(两个可能的值)代表的信息少于十进制数字(10 个可能的值)。
表 1. 二进制与十进制计数的比较
Binary value | Decimal value |
---|---|
0 | 0 |
1 | 1 |
10 | 2 |
11 | 3 |
100 | 4 |
101 | 5 |
… | … |
关于标记数字的第二点看起来非常熟悉!事实上,它与十进制非常相似,以至于可以得出几乎相同的解释二进制数的公式。只需将每个指数底部的 10 替换为 2:
(dN-1 × 2N-1) + (dN-2 × 2N-2) + … + (d2 × 22) + (d1 × 21) + (d0 × 20)
应用此公式会产生任何二进制数的 无符号 解释。例如,取 0b1000:
(1 × 23) + (0 × 22) + (0 × 21) + (0 × 20)
= 8 + 0 + 0 + 0 = 8
这是一个更长的单字节示例,0b10110100:
(1 × 27) + (0 × 26) + (1 × 25) + (1 × 24) + (0 × 23) + (1 × 22) + (0 × 21) + (0 × 20)
= 128 + 0 + 32 + 16 + 0 + 4 + 0 + 0 = 180
4.1.3. 十六进制
到目前为止,我们已经研究了两种数字系统:十进制和二进制。十进制因其对人类的舒适性而引人注目,而二进制则与数据在硬件中存储的方式相匹配。值得注意的是,它们的表达能力是相当的。也就是说,没有任何数字可以在一个系统中表示而不能在另一个系统中表示。考虑到它们的等价性,您可能会感到惊讶,我们将讨论另一种数字系统:以 16 为基数的十六进制系统。
有了两个完美的数字系统,您可能想知道为什么我们需要另一个。答案主要是方便。如表 1 所示,二进制位序列快速增长到大量数字。人类往往很难理解仅包含 0 和 1 的长序列。尽管十进制更紧凑,但它的基数 10 与二进制的基数 2 不匹配。
十进制不容易捕获可以使用固定位数表示的范围。例如,假设一台旧计算机使用 16 位内存地址。其有效地址范围为 0b0000000000000000 到 0b1111111111111111。以十进制表示,地址范围从 0 到 65535。显然,十进制表示比长二进制序列更紧凑,但除非您记住它们的转换,否则很难推理十进制数字。在使用 32 位或 64 位地址的现代设备上,这两个问题只会变得更糟!
这些长位序列正是十六进制的 16 进制的亮点。大基数允许每个数字表示足够的信息,使十六进制数变得紧凑。此外,由于基数本身就是 2 的幂 (24 = 16),因此很容易将十六进制映射到二进制,反之亦然。为了完整起见,我们用十进制和二进制同样的方式来分析十六进制:
- 16 进制数字中的任何单个数字都存储 16 个唯一值之一。超过 10 个值对十六进制提出了新的挑战——传统的 10 位数字的最大值为 9。按照惯例,十六进制使用字母来表示大于 9 的值,其中 A 代表 10,B 代表 11,最多 F 代表 15。与其他系统一样,要存储大于 15 的值,数字必须进位到左侧的附加数字。例如,如果一位数字从最大值 (F) 开始,并且我们向其加 1,则结果需要两位数字(0xF + 0x1 = 0x10;请注意,我们使用 0x 表示十六进制数字)。
- 数字中每个数字的位置决定了该数字对于数字的数值的重要性。将从右到左的数字标记为 d0、d1、d2 等,每个连续数字的贡献比下一个数字多 16 倍。
毫不奇怪,解释数字的相同可靠公式也适用于以 16 为基数的十六进制:
(dN-1 × 16N-1) + (dN-2 × 16N-2) + … + (d2 × 162) + (d1 × 161) + (d0 × 160)
例如,要确定 0x23C8 的十进制值:
> (2 × 163) + (3 × 162) + (C × 161) + (8 × 160)
= (2 × 163) + (3 × 162) + (12 × 161) + (8 × 160) = (2 × 4096) + (3 × 256) + (12 × 16) + (8 × 1) = 8192 + 768 + 192 + 8 = 9160
十六进制误解
当您第一次学习系统编程时,您可能不会经常遇到十六进制数字。事实上,您可能找到它们的唯一上下文是表示内存地址。例如,如果您使用printf
的%p
(指针)格式代码打印变量的地址,您将获得十六进制输出。
许多学生经常开始将内存地址(例如,C 指针变量)与十六进制等同起来。虽然您可能习惯以这种方式表示地址,但请记住 它们仍然在硬件中使用二进制存储 ,就像所有其他数据一样!
4.1.4. 存储限制
从概念上讲,无符号整数有无限多个。在实践中,出于多种原因,程序员必须在存储变量之前选择专用于变量的位数:
- 在存储一个值之前,程序必须为其分配存储空间。在 C 中,声明变量会根据其类型告知编译器需要多少内存。
- 硬件存储设备的容量是有限的。虽然系统的主内存通常很大并且不太可能成为限制因素,但 CPU 内用作临时“暂存空间”的存储位置(即寄存器)受到更多限制。 CPU 使用的寄存器受到字大小的限制(通常为 32 或 64 位,具体取决于 CPU 架构)。
- 程序经常将数据从一个存储设备移动到另一存储设备(例如,在 CPU 寄存器和主存储器之间)。随着值变大,存储设备需要更多的电线来在它们之间传递信号。因此,扩展存储会增加硬件的复杂性,并为其他组件留下更少的物理空间。
用于存储整数的位数决定了其可表示值的范围。 图 2 描绘了我们如何概念化无限和有限无符号整数存储空间。
图 2. (a) 无限无符号数轴和 (b) 有限无符号数轴的图示。后者在任一端点“环绕”(溢出)。
尝试向变量存储比变量大小允许的更大的值称为整数溢出。本章将溢出的详细信息推迟到后面的部分。现在,可以把它想象成汽车的里程表,如果它试图增加超过其最大值,它就会“翻转”回零。同样,从零减一得到最大值。
此时,关于无符号二进制,一个自然要问的问题是“N 位可以存储的最大正值是多少?”换句话说,给定一个全 1 的 N 位序列,该序列代表什么值?非正式地推理这个问题,上一节中的分析表明,N 位产生 2N 个唯一的位序列。由于这些序列之一必须表示数字 0,因此会留下 2N - 1 个介于 1 到 2N - 1 之间的正值。因此,N 位无符号二进制数的最大值必须是 2N - 1。
例如,8 位提供 28 = 256 个唯一序列。这些序列之一 0b00000000 被保留为 0,剩下 255 个序列用于存储正值。因此,8 位变量表示 1 到 255 之间的正值,其中最大的是 255。